;;; yisp-scp.el --- provides scp functionality

;; Copyright (C) 2007  Duzy Zhan

;; Author: Duzy Chan <duzy@duzy.ws>
;; Keywords: convenience, lisp, tools

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:
;;

;;; Interfaces:
;; 
;;      `yisp-scp-setup'
;;      `yisp-scp-get'
;;      `yisp-scp-put'
;;      `yisp-scp-clear-password'
;;      

;;; Variables:
;; 
;;      `yisp-scp-command-name'
;;      `yisp-scp-remote-ssh'
;;      `yisp-scp-username'
;;      `yisp-scp-cached-password'
;;      `yisp-scp-remote-file'
;;      `yisp-scp-remote-directory'
;;      `yisp-scp-local-directory'
;;      

;;; Code:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Providing copying files using scp.
;;
(defvar yisp-scp-command-name
  (cond
   ((string= system-type "gnu/linux") "scp")
   ((string= system-type "windows-nt") "pscp"))
  "Keeps the SCP command line name.")
(defvar yisp-scp-remote-ssh nil "Stores the address of remote SSH host.")
(defvar yisp-scp-username nil "Stores the SSH login user id.")
(defvar yisp-scp-cached-password nil "Keeps password if cached.")
(defvar yisp-scp-remote-file nil)
(defvar yisp-scp-remote-directory nil "The remote side working directory.")
(defvar yisp-scp-local-directory nil "The local side working directory.")

(defun yisp-scp--non-empty-string-p(s)
  (and (stringp s) (not (string= s ""))))

(defun yisp-scp--parameters-valid-p()
  (and (yisp-scp--non-empty-string-p yisp-scp-command-name)
       (yisp-scp--non-empty-string-p yisp-scp-remote-ssh)
       (yisp-scp--non-empty-string-p yisp-scp-username)
       (yisp-scp--non-empty-string-p yisp-scp-remote-directory)))

;;;###autoload
(defun yisp-scp-setup-old()
  "Setup SCP parameters."
  (interactive)
  (if (not (yisp-scp--non-empty-string-p yisp-scp-local-directory))
      (setq yisp-scp-local-directory default-directory))
  (let ((scp-cmd-name (read-from-minibuffer "scp command: "
                                            yisp-scp-command-name))
        (ssh (read-from-minibuffer "Remote SSH: " yisp-scp-remote-ssh))
        (username (read-from-minibuffer "Username: " yisp-scp-username))
        (rdir (read-from-minibuffer "Remote Directory: "
                                    yisp-scp-remote-directory))
        (ldir (read-from-minibuffer "Local Directory: "
                                    yisp-scp-local-directory)))
    (setq yisp-scp-command-name scp-cmd-name)
    (setq yisp-scp-remote-ssh ssh)
    (setq yisp-scp-username username)
    (setq yisp-scp-remote-directory rdir)
    (setq yisp-scp-local-directory ldir)
    (if (string= scp-cmd-name "pscp")
        (setq yisp-scp-cached-password
              (read-passwd "Password (leave empty if donot cache it): ")))
    (if (not (yisp-scp--parameters-valid-p))
        (progn (message "yisp-scp setup not finished yet!") nil)
      t)))

(defvar yisp-scp-setup-field-notify
  (lambda (widget &rest ignore)
    (yisp-scp-setup-field-callback widget))
  "")

(defun yisp-scp-setup-field-callback (widget)
  ""
  (let ((tag (widget-get widget :tag))
        (val (widget-value widget)))
    (cond
     ((string= tag "scp-command")
      (setq yisp-scp-command-name val))
     ((string= tag "remote-ssh")
      (setq yisp-scp-remote-ssh val))
     ((string= tag "username")
      (setq yisp-scp-username val))
     ((string= tag "remote-directory")
      (setq yisp-scp-remote-directory val))
     ((string= tag "local-directory")
      (setq yisp-scp-local-directory))
     )))

;;;###autoload
(defun yisp-scp-setup ()
  "Setup SCP parameters."
  (interactive)
  (switch-to-buffer "*yisp-scp-setup*")
  (kill-all-local-variables)
  (erase-buffer)
  (widget-insert "[yisp] Seting up.\n\n")
  (widget-insert "Basic information:\n")
  (widget-create 'editable-field
                 :size 25 :tag "scp-command"
                 :format "\tSCP command:\t%v\n"
                 :notify yisp-scp-setup-field-notify
                 (if yisp-scp-command-name yisp-scp-command-name ""))
  (widget-create 'editable-field
                 :size 25 :tag "remote-ssh"
                 :format "\tSSH Host:\t%v\n"
                 :notify yisp-scp-setup-field-notify
                 (if yisp-scp-remote-ssh yisp-scp-remote-ssh ""))
  (widget-insert "\nLogin information:\n")
  (widget-create 'editable-field
                 :size 25 :tag "username"
                 :format "\tUsername:\t%v\n"
                 :notify yisp-scp-setup-field-notify
                 (if yisp-scp-username yisp-scp-username ""))
  (widget-insert "\nPositon:\n")
  (widget-create 'editable-field
                 :size 30 :tag "remote-directory"
                 :format "\tRemote directory:\t%v\n"
                 :notify yisp-scp-setup-field-notify
                 (if yisp-scp-remote-directory yisp-scp-remote-directory ""))
  (widget-create 'editable-field
                 :size 30 :tag "local-directory"
                 :format "\tLocal directory:\t%v\n"
                 :notify yisp-scp-setup-field-notify
                 (if yisp-scp-local-directory yisp-scp-local-directory ""))
  (widget-create 'push-button
                 :tag "ok" :format "\n    [OK]\n")
  (widget-setup))

;;;###autoload
(defun yisp-scp-save-parameters-to-dot-emacs ()
  ""
  (yisp-error "unimplementes."))

(defun yisp-scp--ensure-parameters-valid()
  "Setup SCP parameters if they are not yet avalible"
  (if (not (yisp-scp--parameters-valid-p))
      (yisp-scp-setup))
  (yisp-scp--parameters-valid-p))

;;;###autoload
(defun yisp-scp-clear-password()
  "Clears the cached password."
  (interactive)
  (if (y-or-n-p "Clear the chached password? ")
      (setq yisp-scp-cached-password nil)
    (if (yisp-scp--non-empty-string-p yisp-scp-cached-password)
        (message "Cached password is valid."))))

(defun yisp-scp--password()
  "Returns the password if it cached, or prompt to be entered if not."
  (unless (yisp-scp--non-empty-string-p yisp-scp-cached-password)
    (setq yisp-scp-cached-password
          (read-passwd "Password (leave empty if donot cache it): ")))
  yisp-scp-cached-password)

(defun yisp-scp--convert-to-space-separated-file-list(files)
  (cond
   ((listp files)
    (let ((f files) (str nil))
      (if f (progn (setq str (format "%s" (car f)))
                   (setq f (cdr f))))
      (while f
        (setq str (format "%s %s" str (car f)))
        (setq f (cdr f)))
      str))
   ((stringp files)
    (let ((idx 0) (last-sep 0)
          (end (length files))
          (str "") (c nil))
      (while (< idx end)
        (setq c (elt files idx))
        (if (or (char-equal c ?,)
                (char-equal c 59) ;; ?;
                ;;(char-equal c ? )
                )
            (progn
              ;;(setq str (concat str (substring files last-sep (1+ idx))))
              (setq str (format "%s %s" str (substring files
                                                       (if (= 0 last-sep) 0
                                                         (1+ last-sep))
                                                       idx)))
              (setq last-sep idx)))
        (setq idx (1+ idx)))
      (format "%s %s " str (substring files (1+ last-sep)))))))
;; testing yisp-scp--convert-to-space-separated-file-list:
;;(yisp-scp--convert-to-space-separated-file-list '(a b c d e f))
;;(yisp-scp--convert-to-space-separated-file-list "adf,dfe;dfc,aed,sqe,ddf")

(defun yisp-scp--make-scp-command-line(str-files action)
  "Generate scp command line."
  (let ((scp-cmd nil) (password (yisp-scp--password)))

    (if (and (string= yisp-scp-command-name "pscp")
             (not (yisp-scp--non-empty-string-p password)))
        (yisp-error "SSH Password must not be empty."))

    (cond
     ;; action 'put'
     ((eq action 'put)
      (cond
       ((string= yisp-scp-command-name "pscp")
        (setq scp-cmd (format "pscp -pw %s -p -r %s %s@%s:%s"
                              password str-files yisp-scp-username
                              yisp-scp-remote-ssh yisp-scp-remote-directory)))
       ((string= yisp-scp-command-name "scp")
        (setq scp-cmd (format "scp %s %s@%s:%s"
                              str-files yisp-scp-username
                              yisp-scp-remote-ssh yisp-scp-remote-directory)))
       (t (yisp-error "Unsupported scp command: %s" yisp-scp-command-name))))

     ;; action 'get'
     ((eq action 'get)
      (cond
       ((string= yisp-scp-command-name "pscp")
        (setq scp-cmd (format "pscp -pw %s -p -r %s@%s:%s/%s %s"
                              password yisp-scp-username
                              yisp-scp-remote-ssh
                              yisp-scp-remote-directory str-files
                              yisp-scp-local-directory)))
       ((string= yisp-scp-command-name "scp")
        (setq scp-cmd (format "scp %s@%s:$s/%s %s"
                              yisp-scp-username
                              yisp-scp-remote-ssh
                              yisp-scp-remote-directory str-files
                              yisp-scp-local-directory)))
       (t (yisp-error "Unsupported scp command: %s" yisp-scp-command-name))))

      )
    scp-cmd))

(defun yisp-scp--internal(action)
  "Internal implementation for `yisp-scp-get' and `yisp-scp-put'."
  (if (yisp-scp--ensure-parameters-valid)
      (let ((password (yisp-scp--password))
            (cmd-line nil) (cmd-succeed nil)
            (pre-cmd-msg nil))
        
        (if (and (string= yisp-scp-command-name "pscp")
                 (not (yisp-scp--non-empty-string-p password)))
            (yisp-error "SSH Password must not be empty."))
        
        (cond
         ((eq action 'put)
          (let ((file nil) (files '()) (str-files ""))
            (while (yisp-scp--non-empty-string-p
                    ;; TODO: promts a file-list history
                    (setq file (read-from-minibuffer "Upload: ")))
              (push file files))
            (setq str-files
                  (yisp-scp--convert-to-space-separated-file-list files))
            (setq cmd-line (yisp-scp--make-scp-command-line str-files 'put))
            (setq pre-cmd-msg "Storing files...")
            ))
         ((eq action 'get)
          (let ((str-file ""))
            (setq str-file (read-from-minibuffer "Download: "))
            (setq cmd-line (yisp-scp--make-scp-command-line str-file 'get))
            (setq pre-cmd-msg "Download files..."))))
        
        (if (not (yisp-scp--non-empty-string-p cmd-line))
            (yisp-error "Faild to find scp command!"))

        (let ((old-def-dir nil))
          (if (yisp-scp--non-empty-string-p yisp-scp-local-directory)
              (if (cd yisp-scp-local-directory)
                  (setq old-def-dir default-directory)))
          (message pre-cmd-msg)
          (setq ok (eshell-command cmd-line))
          ;;(if ok (message "Done.")
          ;;  (message "Failed storing!"))
          (if old-def-dir (cd old-def-dir)))
        
        ok)))

;;;###autoload
(defun yisp-scp-put()
  "Save FILES onto remote SSH host.

Before using `yisp-scp-put', the scp parameters should be setuped,
see `yisp-scp-setup'.

The working directories on the remote host and localhost are store in
`yisp-scp-remote-directory' and `yisp-scp-local-director'.
"
  (interactive)
  (yisp-scp--internal 'put))

;;;###autoload
(defun yisp-scp-get()
  "Load FILES from remote host."
  (interactive)
  (yisp-scp--internal 'get))

(provide 'yisp-scp)
;;; yisp-scp.el ends here
